﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EntityBinder.Interface;

namespace EntityBinder.Json
{
	class JsonReader : IEntityReader
	{
		private IEnumerable<IEntityElement> m_elementsRoot = null;

		internal JsonReader()
		{
			
		}

		internal JsonReader(IEntityElement _element)
		{
			m_elementsRoot = new List<IEntityElement>() { _element };
		}

		internal JsonReader(IEnumerable<IEntityElement> _elements)
		{
			m_elementsRoot = _elements;
		}

		public object Read(System.IO.Stream _inputStream)
		{
			return Read(_inputStream, m_elementsRoot);
		}

		public object Read(System.IO.Stream _inputStream, IEntityElement _element)
		{
			IEnumerable<IEntityElement> elements = new List<IEntityElement>() { _element };
			return Read(_inputStream, elements);
		}

		public object Read(System.IO.Stream _inputStream, IEnumerable<IEntityElement> _elements)
		{
			if(_elements == null || _elements.Count() == 0)
			{
				throw new EntityBinderException("JsonReader", "Read", "Can not read without any rules");
			}

			byte[] rawData = new byte[_inputStream.Length];
			_inputStream.Read(rawData, 0, rawData.Length);
			string jsonData = Encoding.UTF8.GetString(rawData, 0, rawData.Length);

			//find the correct root element
			JsonElement currentRead = JsonElement.ParseJson(jsonData).GetAsContainer();
			IDictionary<string, JsonElement> dictionaryRead = currentRead.GetDictionary();
			IEntityElement rootElement = null;

			foreach (IEntityElement e in _elements)
			{
				int correspondingChildren = (from child in e.Children
											where dictionaryRead.ContainsKey(child.Identifier)
											select child).Count();
				if(correspondingChildren == dictionaryRead.Count)
				{
					rootElement = e;
					break;
				}
			}

			if(rootElement == null)
			{
				throw new EntityBinderException("JsonReader", "Read", "Can not find a rule to read Json stream");
			}

			// parse stream to object.
			object resultData = rootElement.Read(null, null);
			Parse(rootElement, currentRead, resultData);
			return resultData;
		}

		public T Read<T>(System.IO.Stream _inputStream) where T : class
		{
			return Read(_inputStream) as T;
		}

		public T Read<T>(System.IO.Stream _inputStream, IEntityElement _element) where T : class
		{
			return Read(_inputStream, _element) as T;
		}

		public T Read<T>(System.IO.Stream _inputStream, IEnumerable<IEntityElement> _elements) where T : class
		{
			return Read(_inputStream, _elements) as T;
		}

		private void Parse(IEntityElement _rule, JsonElement _data, object _result)
		{
			if(_data.IsDictionary())
			{
				Dictionary<IEntityElement, bool> optional = new Dictionary<IEntityElement, bool>();
				foreach(IEntityElement e in _rule.Children)
				{
					optional.Add(e, false);
				}

				foreach(KeyValuePair<string, JsonElement> d in _data.GetDictionary())
				{
					IEnumerable<IEntityElement> children = from e in _rule.Children
															where e.Identifier == d.Key
															select e;
					if(children.Count() != 1)
					{
						throw new EntityBinderException("JsonReader", "Parse", "There is not one child rule for element " + d.Key);
					}

					IEntityElement child = children.Single();
					optional[child] = true;

					JsonElement valueElement = d.Value;
					if(valueElement.IsValue())
					{
						child.Read(_result, valueElement.GetAsValue());
					}
					else if(valueElement.IsContainer())
					{
						object childData = child.Read(_result, null);
						Parse(child, valueElement.GetAsContainer(), childData);
					}
					else if(valueElement.IsArray())
					{
						foreach(JsonElement v in valueElement.GetAsArray())
						{
							if(v.IsContainer())
							{
								object childData = child.Read(_result, null);
								Parse(child, v.GetAsContainer(), childData);
							}
							else if(v.IsValue())
							{
								child.Read(_result, v.GetAsValue());
							}
							else
							{
								throw new EntityBinderException("JsonReader", "Parse", "Can not process type " + v.Type + " in a json array");
							}
						}
					}
					else
					{
						throw new EntityBinderException("JsonReader", "Parse", "Can not process type " + valueElement.Type + " as in dictionary level");
					}
				}

				IEnumerable<IEntityElement> missingElement = from opt in optional
															 where !opt.Key.Optional && !opt.Value
															 select opt.Key;
				if(missingElement.Count() > 0)
				{
					StringBuilder types = new StringBuilder();
					foreach(IEntityElement e in missingElement)
					{
						types.Append(e.Identifier);
						types.Append(", ");
					}
					types.Remove(types.Length - 2, 2);
					throw new EntityBinderException("JsonReader", "Parse", "Missing non optional fields " + types + " in " + _rule.Identifier);
				}
			}
			else
			{
				throw new EntityBinderException("JsonReader", "Parse", "Can not process type " + _data.Type + " as top level");
			}
		}
	}
}
